﻿using System;
using System.IO;
using System.IO.Compression;

using RayDen.Library.Data.Xml;
using RayDen.Library.Entity.Frames;

namespace RayDen.Library.Data
{

    public class ArchiveFrameService
    {
        public static FrameArchive OpenArchive(string archivePath, string openFolder)
        {
            return new FrameArchive(archivePath, GetFrameName(archivePath), openFolder);
        }

        private static string GetFrameName(string archivePath)
        {
            return string.Format("{0}.rdf", Path.GetFileNameWithoutExtension(archivePath));
        }
    }

    public class FrameArchive : IDisposable
    {
        protected FrameDescription frame;
        protected ArchiveFileSession archive;
        public FrameDescription Frame
        {
            get { return frame; }
        }


        public FrameArchive(string archivePath, string frameName, string tempFolder)
        {
            archive = new ArchiveFileSession(archivePath);
            archive.Extract(tempFolder, true);
            using (var frameFile = new FileStream(frameName, FileMode.Open, FileAccess.Read))
            {
                using (var reader = new StreamReader(frameFile))
                    frame = (FrameDescription)SerializationService.Deserialize(reader.ReadToEnd(), typeof(FrameDescription));
            }
            frame.WorkingDir = tempFolder;
        }

        public void Dispose()
        {
            archive.Dispose();
        }
    }


    /// <summary>
    /// Represents archiving session - ZipArchive, WorkingFolder, File List. Should pack all scene-related data into
    /// </summary>
    public class ArchiveFileSession : IDisposable
    {
        protected bool extracted;
        protected int bufferSize = 128;
        protected string workingDir;
        protected ZipArchive archive;

        public bool IsOpen
        {
            get { return extracted; }
        }

        public ArchiveFileSession(string fileName)
        {
            bool createNew = !File.Exists(fileName);
            this.archive = new ZipArchive(createNew ? File.Create(fileName) : File.Open(fileName, FileMode.Open));
        }

        public string GetFullPath(string fileName)
        {
            return Path.Combine(workingDir, fileName);
        }

        public void AddFile(string fileName)
        {
            int offset = 0;
            var entry = this.archive.CreateEntry(fileName);
            using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                var buffer = new byte[bufferSize];

                using (var sw = new BinaryWriter(entry.Open()))
                {
                    while (fs.Position != fs.Length - 1)
                    {
                        int readBytes = fs.Read(buffer, offset, bufferSize);
                        sw.Write(buffer, 0, readBytes);
                        offset += readBytes;
                    }
                }

            }
        }


        public void Extract(string workDir, bool keepDirectory)
        {
            this.workingDir = workDir;
            var td = Directory.GetCurrentDirectory();
            var buffer = new byte[bufferSize];
            if (!Directory.Exists(workingDir))
            {
                Directory.CreateDirectory(workingDir);
            }
            Directory.SetCurrentDirectory(workingDir);
            foreach (var arcEntry in this.archive.Entries)
            {
                using (var br = new BinaryReader(arcEntry.Open()))
                {
                    string fileName = arcEntry.FullName;
                    using (var fs = new FileStream(GetFullPath(fileName), FileMode.Create, FileAccess.Write))
                    {
                        bool done = false;
                        while (!done)
                        {
                            buffer = br.ReadBytes(bufferSize);
                            int readBytes = buffer.Length;
                            fs.Write(buffer, 0, readBytes);
                            //offset += readBytes;
                            done = readBytes == 0;
                        }
                    }
                }
            }
            if (!keepDirectory)
                Directory.SetCurrentDirectory(td);

            extracted = true;
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (Directory.Exists(workingDir))
            {
                foreach (var tempFile in Directory.EnumerateFiles(workingDir))
                {
                    File.Delete(tempFile);
                }
                Directory.Delete(workingDir);
            }

            if (this.archive != null)
            {
                this.archive.Dispose();
            }
        }

        #endregion
    }
}
